package com.hcc.gateway.app.filter;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.hcc.gateway.app.constant.HccGatewayAppConstant;
import com.hcc.gateway.app.dubbo.DubboGenericClient;
import com.hcc.gateway.common.domain.DubboReq;
import com.hcc.gateway.common.utils.DubboUtils;
import com.hcc.gateway.domain.po.ApiConfigPo;
import com.hcc.gateway.domain.vo.ParamTypeConfigVo;
import com.hcc.gateway.netty.context.GatewayExchange;
import com.hcc.gateway.netty.filter.GatewayFilter;
import com.hcc.gateway.netty.filter.GatewayFilterChain;
import com.hcc.gateway.netty.support.GatewayConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * HttpRoutingFilter
 *
 * @author shengjun.hu
 * @date 2021/12/28
 */
@Slf4j
@Component
public class DubboRoutingFilter implements GatewayFilter {

    @Override
    public int getOrder() {
        return 251;
    }

    @Override
    public void filter(GatewayExchange exchange, GatewayFilterChain chain) {
        log.info("进入DubboRoutingFilter !");

        URI uri = exchange.getAttributeByName(GatewayConstants.GATEWAY_REQUEST_URI_ATTR, URI.class);
        ApiConfigPo apiConfig = exchange.getAttributeByName(HccGatewayAppConstant.HCC_API_CONFIG, ApiConfigPo.class);
        if (!isDubboRequest(uri.getScheme().toLowerCase())) {
            log.info("不是dubbo协议，跳过DubboRoutingFilter !");
            chain.filter(exchange);
            return;
        }

        Object paramObj = exchange.getAttributeByName(HccGatewayAppConstant.HCC_REQ_PARAM);

        DubboReq dubboReq = this.initDubboReq(apiConfig, paramObj);
        Object result = DubboGenericClient.invoke(dubboReq);

        exchange.addAttribute(HccGatewayAppConstant.HCC_RESPONSE_OBJECT, result);

        chain.filter(exchange);
    }

    private boolean isDubboRequest(String scheme) {
        return "dubbo".equals(scheme) || "zookeeper".equals(scheme);
    }

    /**
     * 初始化dubbo请求体
     * @param apiConfigPo
     * @param paramObj
     * @return
     */
    private DubboReq initDubboReq(ApiConfigPo apiConfigPo, Object paramObj) {
        DubboReq dubboReq = DubboUtils.parseUrl(apiConfigPo.getTargetUrl());
        dubboReq.setApplicationName("hcc-gateway-consumer");

        List<ParamTypeConfigVo> paramTypeConfigs = StrUtil.isEmpty(apiConfigPo.getParamTypeConfig()) ? Collections.emptyList()
                : JSON.parseArray(apiConfigPo.getParamTypeConfig(), ParamTypeConfigVo.class);
        dubboReq.setParamType(paramTypeConfigs.stream().map(ParamTypeConfigVo::getType).toArray(String[]::new));
        Object[] params;
        if (paramTypeConfigs.size() == 1) {
            ParamTypeConfigVo paramTypeConfig = paramTypeConfigs.get(0);
            if (paramTypeConfig.getCustomer()
                    || this.isCollection(paramTypeConfig.getType())
                    || this.isMap(paramTypeConfig.getType())) {
                // 是用户自定义类型，或者集合，或者Map
                params = new Object[] { paramObj };
            } else {
                // 其它视为基础类型，从json中取出
                Map<String, Object> reqParamMap = (Map<String, Object>) paramObj;
                params = new Object[] { reqParamMap.get(paramTypeConfig.getName()) };
            }
        } else {
            Map<String, Object> reqParamMap = (Map<String, Object>) paramObj;
            params = paramTypeConfigs.stream().map(c -> reqParamMap.get(c.getName())).toArray();
        }

        dubboReq.setParam(params);

        return dubboReq;
    }

    /**
     * 是否是集合类型
     * @param type
     * @return
     */
    private boolean isCollection(String type) {
        try {
            Class<?> clazz = Class.forName(type);
            return Collection.class.isAssignableFrom(clazz);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private boolean isMap(String type) {
        try {
            Class<?> clazz = Class.forName(type);
            return Map.class.isAssignableFrom(clazz);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

}
